//============================================================================
// Name        : TP4_CLIENTE_JUGADOR.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include "Config.h"
#include "Partida.h"
#include "Socket.h"
#include <ostream>
#include <sstream>
#include <istream>
#include "ProductorAccionesAEjecutar.h"
#include "ConsumidorAccionesAEjecutar.h"
#include "EscuchaTeclasIngresadas.h"
#include "ProductorAcciones.h"
#include "RefreshDisplay.h"
#include "IngresoDeNombre.h"
#include "SDL/SDL.h"
#include "SDL/SDL_image.h"
#include "SDL/SDL_ttf.h"
#include <X11/Xlib.h>

using namespace std;


/* Variables globales al programa */
bool fin_logico = true;						// Variable para indicar el fin logico del programa.
char semShmName[20];						// Nombre del semaforo de la memoria compartida de este servidor partida.
int puerto;
bool port;
bool IP;
string host;
map<int,int> controles;
char nombre[MSJ_LENGHT] = {0};
Partida *juego;								// Partida en si misma, con los objetos acciones y demas.
Socket  *conexion;							// Socket de conexion a los jugadores.
Socket  *socket2;							// Copia para el signal handler.
ProductorAccionesAEjecutar	*pAccToExec;
ConsumidorAccionesAEjecutar *cAccToExec;
EscuchaTeclasIngresadas		*inputTeclas;
ProductorAcciones 			*pAcciones;
RefreshDisplay				*pantalla;
SDL_Surface *pantallaBase;
SDL_Surface *imagen1;
SDL_Surface *imagen2;
TTF_Font *font = NULL;
SDL_Color textColor = { 0xFF, 0x01, 0x01 };
SDL_Surface *pantallaInicio = NULL;
SDL_Surface *pantallaEspera = NULL;
SDL_Surface *pantallaFinTorneo = NULL;
SDL_Surface *pantallaError   = NULL;
SDL_Surface *message1 = NULL;
SDL_Surface *messageEspera = NULL;
SDL_Surface *messageFinTorneo = NULL;
SDL_Surface *messagePuntaje = NULL;
SDL_Surface *messageFinError = NULL;
SDL_Event event;

/* Pone la pantalla de espera a la proxima partida */
bool pantallaDeEspera();
/* Recibe el nombre por linea de comandos */
bool pantallaDeInicio();
/* Muestra la pantalla de fin de torneo */
void pantallaDeFinTorneo();
/* Muestra la pantalla de error */
void pantallaDeError();
/* Aplica una superficie sobre otra */
void apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip);
/* Toma el archivo de configuracion y setea sus parametros en el servidor, tambien los valida. */
bool configuracionesGenerales();
/* Crea y conecta el socket para comenzar la conexion. */
bool inicializarSocket();
/* Inicia la partida, configurando los hilos de escucha y escritura al servidor partida. */
bool iniciarJuego();
/* Inicia la partida, arrancando los hilos y esperando el comando comenzar del servidor */
void inicializarPartida();
/* Se encarga de liberar la memoria compartida, semaforos y cualquier recurso que pueda dejar tomado por error. */
void liberarRecursos();
/* Handler que toma las señal de finalización y la trasmite al thread. */
void signalHandler(int signal);
/* Inicia la pantalla con la configuracion y las imagenes base */
bool iniciarVideo();
/* Carga una imagen en un superficie */
SDL_Surface *load_image(const char *path);
void cleanup();
void salir();

/* Proceso princilpal */
int main() {
	/* Para que la libreria pueda ser accedida de distintos threads */
	XInitThreads();

	signal(SIGURG, signalHandler);
	signal(SIGABRT, signalHandler);
	signal(SIGFPE, signalHandler);
	signal(SIGILL, signalHandler);
	signal(SIGTERM, signalHandler);

	/* Comienzo tomando las configuraciones generales del archivo de configuracion */
	if(!configuracionesGenerales())
		return CONST_UNO;

	/* Luego continuo iniciando la conexion al servidor torneo por el socket */
	if(!inicializarSocket())
		return CONST_UNO;

	/* Inicia la pantalla con la configuracion y las imagenes base */
	if(!iniciarVideo())
		salir();

	/* Inicia la partida, configurando los hilos de escucha y escritura al servidor partida. */
	if(!iniciarJuego())
		salir();

    /* Mientras siga el juego y no sali */
    while(juego->isActivo() && !juego->isFinTorneo()){

    	/* Inicializo los valores de la partida y quedo a la espera del comando comenzar del servidor */
    	inicializarPartida();

    	/* Espera a que comience la partida */
    	while(!juego->isFinTorneo() && juego->isStoped()){
    		usleep(1000);
    	}

    	/* Mientras que continue la partida y no termine el torneo */
    	while(!juego->isFinTorneo() && !juego->isStoped()){
    		usleep(SPEED_ANIM * CONST_CINCO);
    	}

        /* Logueo */
        cout<<"[FIN] - La partida a finalizado. "<<endl;

    	/* Fin de  partida, esperando la siguiente, si no termino el torneo */
    	if(!juego->isFinTorneo() && juego->isActivo())
    		pantallaDeEspera();
    }

    if(juego->isFinError())
    	/* Si termino con error muestro la pantalla de error */
    	pantallaDeError();
    else if(juego->isFinTorneo())
    	/* Si termino el torneo muestro la pantalla de fin */
    	pantallaDeFinTorneo();
    else
    	/* Logueo */
    	cout<<"[FIN] - Se desconecta el cliente del juego. "<<endl;

    /* Libero todos los recursos del servidor */
    liberarRecursos();

    /* Salgo */
	exit(0);

}

/* Recibe el nombre por linea de comandos */
bool pantallaDeEspera(){
    /* Cargo el fondo de pantalla inicial */
	pantallaEspera = load_image("pantallaEspera.bmp");
	if(pantallaEspera == NULL) {
		cout  << "[ERROR] - No se pudo levantar la imagen de espera: " << SDL_GetError() << endl;
		return CONST_FALSE;
	 }

    //Set the message
    messageEspera = TTF_RenderText_Solid( font, "ESPERANDO NUEVA PARTIDA", textColor );

	//Apply the background
	apply_surface( 0, 0, pantallaEspera, pantallaBase, NULL);
	//Show the message
	apply_surface( ( ANCHO_RESOLUCION - messageEspera->w )/2 , (( ALTO_RESOLUCION- messageEspera->h ) / 2), messageEspera, pantallaBase , NULL);

	//Update the screen
	if( SDL_Flip( pantallaBase ) == -1 ){
		return CONST_FALSE;
	}

    return CONST_TRUE;
}

/* Muestra la pantalla de fin con error del torneo */
void pantallaDeError(){
    /* Cargo el fondo de pantalla inicial */
	pantallaError = load_image("pantallaError.bmp");
	if(pantallaError == NULL) {
		cout  << "[ERROR] - No se pudo levantar la imagen de error: " << SDL_GetError() << endl;
		return;
	 }

    /* Seteo el mensaje, incluyendo el puntaje del jugador */
	messageFinError = TTF_RenderText_Solid( font, "FIN TORNEO - ERROR", textColor );

	/* Bliteo el fondo */
	apply_surface( 0, 0, pantallaError, pantallaBase, NULL);
	/* bliteo los mensajes */
	apply_surface( ( ANCHO_RESOLUCION - messageFinError->w )/2 , ((( ALTO_RESOLUCION- (messageFinError->h/2) ) / 5) * 4), messageFinError, pantallaBase , NULL);

	//Update the screen
	if( SDL_Flip( pantallaBase) == -1 ){
		return ;
	}

	/* Doy unos segundos para que los lea */
	sleep(2);
}

/* Muestra la pantalla de fin de torneo */
void pantallaDeFinTorneo(){
    /* Cargo el fondo de pantalla inicial */
	pantallaFinTorneo = load_image("pantallaFinTorneo.bmp");
	if(pantallaFinTorneo == NULL) {
		cout  << "[ERROR] - No se pudo levantar la imagen de espera: " << SDL_GetError() << endl;
		return;
	 }

    /* Seteo el mensaje, incluyendo el puntaje del jugador */
    messageFinTorneo = TTF_RenderText_Solid( font, "FIN TORNEO - RANKING:", textColor );
    messagePuntaje = TTF_RenderText_Solid( font, Partida::getInstance()->getRankingJugador().c_str(), textColor );

	/* Bliteo el fondo */
	apply_surface( 0, 0, pantallaFinTorneo, pantallaBase, NULL);
	/* bliteo los mensajes */
	apply_surface( ( ANCHO_RESOLUCION - messageFinTorneo->w )/2 , ((( ALTO_RESOLUCION- (messageFinTorneo->h/2) ) / 5) * 3), messageFinTorneo, pantallaBase , NULL);
	apply_surface( ( ANCHO_RESOLUCION - messagePuntaje->w )/2 , ((( ALTO_RESOLUCION- (messagePuntaje->h/2) ) / 5) * 4), messagePuntaje, pantallaBase , NULL);

	//Update the screen
	if( SDL_Flip( pantallaBase ) == -1 ){
		return ;
	}

	/* Doy unos segundos para que los lea */
	sleep(2);
}

/* Inicia la pantalla con la configuracion y las imagenes base */
bool iniciarVideo(){
	/* Inicia la pantalla de video */
	if (SDL_Init(SDL_INIT_VIDEO) != 0){
		cout << "[ERROR] - No se pudo levantar la imagen del local: " << SDL_GetError() << endl;
		return CONST_FALSE;
	}


    /* Inicializo las fuentes sdls */
    if( TTF_Init() == -1 ){
		cout << "[ERROR] - No se pudieron levantar las fuentes" << SDL_GetError() << endl;
        return CONST_FALSE;
    }

	/*De esta forma me aseguro que al cerrarse el programa se cierren también todos
	los subsitemas de SDL*/
	atexit(SDL_Quit);

	/*Inicializo el modo de video con una pantalla de 800 x 600 con 32 bytes de
	profundidad de color  LO CAMBIE a 16 no sera mas rapido asi??*/
	pantallaBase = SDL_SetVideoMode(ANCHO_RESOLUCION, ALTO_RESOLUCION, 16, 0);
	if (pantallaBase == NULL){
		cout  << "[ERROR] - No se pudo inicializar el modo de video: " << SDL_GetError() << endl;
		return CONST_FALSE;
	 }

	//Bloqueo el uso de la superficie
	SDL_LockSurface(pantallaBase);

	/* Cargo la imagen que contiene todos los sprites de Felix 1 */
	imagen1 = load_image("SpritesFelix1.png");
	if(imagen1 == NULL){
		cout  << "[ERROR] - No se pudo levantar la imagen del local: " << SDL_GetError() << endl;
		return CONST_FALSE;
	 }

	/* Cargo la imagen que contiene todos los sprites de Felix 2 */
	imagen2 = load_image("SpritesFelix2.png");
	if(imagen2 == NULL) {
		cout  << "[ERROR] - No se pudo levantar la imagen del visitante: " << SDL_GetError() << endl;
		return CONST_FALSE;
	 }

    /* Cargo el fondo de pantalla inicial */
    pantallaInicio = load_image("pantallaInicio.bmp");
	if(pantallaInicio == NULL) {
		cout  << "[ERROR] - No se pudo levantar la pantalla de inicio: " << SDL_GetError() << endl;
		return CONST_FALSE;
	 }

	/* Abre la fuente a utilizar */
    font = TTF_OpenFont( "fuente.ttf", 30 );
    if( font == NULL ){
        return CONST_FALSE;
    }

	//Ignoro los eventos que no uso
	SDL_EventState(SDL_MOUSEMOTION,SDL_IGNORE);
	SDL_EventState(SDL_MOUSEBUTTONDOWN,SDL_IGNORE);
	SDL_EventState(SDL_MOUSEBUTTONUP,SDL_IGNORE);
	SDL_EventState(SDL_KEYUP,SDL_IGNORE);

    //Set the window caption
    //SDL_WM_SetCaption( "High Score", NULL );

	//Desbloqueo la superficie
	SDL_UnlockSurface(pantallaBase);

    /* Update the screen */
	apply_surface( 0, 0, pantallaInicio, pantallaBase, NULL);
    if( SDL_Flip( pantallaBase ) == -1 ){
        return 1;
    }

	return CONST_TRUE;
}

/* Toma el archivo de configuracion y setea sus parametros en el servidor, tambien los valida. */
bool configuracionesGenerales(){
	/* Tomo los parametros de configuracion */
	Config oConfig(ARCH_CONFIG);
	controles   = oConfig.obtenerControles();
	puerto		= oConfig.obtenerPuerto();
	host 		= oConfig.obtenerHost();

	/* Los valido */
	if(puerto > MAX_PUERTO_SERVER || puerto < MIN_PUERTO_SERVER){
		cout << "[ERROR] - El valor del puerto debe estar entra 65536 y 10000" << endl;
		return CONST_FALSE;
	}

	return CONST_TRUE;
}

/* Crea y conecta el socket para comenzar la conexion. */
bool inicializarSocket(){
	//Creo el socket del cliente.
	conexion = new Socket(puerto, host);
	socket2 = conexion;

	//Valido que se pueda usar la ip
	if (conexion->setStruct_Socket(host) == 0){
		IP 	 = CONST_TRUE;
		port = CONST_TRUE;
	}
	// Si los parámetros son correctos inicio.
	if(port == true && IP == true){
		//Conecto al servidor
		if(conexion->connectToServer() == 1){
		    cout << "[ERROR] - Error de conexion, verifique el servidor para ip y puerto ingresado." <<endl;
			return CONST_FALSE;
		}
	}else{
		cout << "[ERROR] - Parámetros Incorrectos. " << endl;
		return CONST_FALSE;
	}

	return CONST_TRUE;
}

/* Inicia la partida, confiurando los hilos de escucha y escritura al servidor partida. */
bool iniciarJuego(){
	/* Recibe el nombre por la pantalla */
	pantallaDeInicio();
	cleanup();

	/* Creo la estructura de t_jugador al server */
	t_jugador jugador;
	strcpy(jugador.nombre, nombre);
	jugador.socket_id = CONST_CERO;
	jugador.conectado = CONST_FALSE;
	jugador.puntaje   = CONST_CERO;
	jugador.ranking   = CONST_CERO;

	/* Envio el nombre al servidor partida. */
	if(conexion->write((void *)&jugador, sizeof(jugador)) == CONST_MENOS_UNO)
		return CONST_FALSE;

	/* Ahora creo la instancia de la partida y los threads, pero sin iniciarlos */
	juego = Partida::getInstance();

	/* Inicio la partida */
	juego->setMiNombre(nombre);
	juego->setControles(&controles);
	juego->setPantallaBase(pantallaBase);
	juego->setImagen1(imagen1);
	juego->setImagen2(imagen2);

	/* Instancia  el hilo de escucha de teclas */
	inputTeclas= new EscuchaTeclasIngresadas(DEATTACH);

	/* Arranco el juego para poder loopear esperando el comando de inicio del servidor */
	juego->iniciarPartida();

	/* Arranco el thread de teclas */
	inputTeclas->start();

    /* Logueo */
    cout << "[INFO] - Jugador conectado al torneo. " << endl;

	return CONST_TRUE;
}

/* Inicia la partida */
void inicializarPartida(){
    /* Mato los threads */
	if(pAcciones!=NULL)
		pthread_kill(pAcciones->get_id(), SIGURG);
	if(cAccToExec!=NULL)
		pthread_kill(cAccToExec->get_id(),SIGABRT);
	if(pAccToExec!=NULL)
		pthread_kill(pAccToExec->get_id(), SIGFPE);
	if(pantalla!=NULL)
		pthread_kill(pantalla->get_id(), SIGILL);

	pAccToExec = new ProductorAccionesAEjecutar(DEATTACH);
	cAccToExec = new ConsumidorAccionesAEjecutar(DEATTACH);
	pAcciones  = new ProductorAcciones(DEATTACH);
	pantalla   = new RefreshDisplay(DEATTACH);

	/* Ahora los configuro pasandole los datos que necesiten */
	pAccToExec->setConexion(conexion);
	pAcciones->setConexion(conexion);

	/* Lanzo los 4 threads de atencion y ejecucion de acciones */
	pAcciones->start();
	pantalla->start();
	cAccToExec->start();
	pAccToExec->start();
}

/* Se encarga de liberar la memoria compartida, semaforos y cualquier recurso que pueda dejar tomado por error. */
void liberarRecursos(){
	//TODO: Ver si falta liberar algo.
    /* Mato los threads */
	if(inputTeclas!=NULL)
		pthread_kill(inputTeclas->get_id(),SIGTERM);
	if(pAcciones!=NULL)
		pthread_kill(pAcciones->get_id(), SIGURG);
	if(cAccToExec!=NULL)
		pthread_kill(cAccToExec->get_id(),SIGABRT);
	if(pAccToExec!=NULL)
		pthread_kill(pAccToExec->get_id(), SIGFPE);
	if(pantalla!=NULL)
		pthread_kill(pantalla->get_id(), SIGILL);
/*
	if(imagen1 != NULL)
    	SDL_FreeSurface( imagen1 );
    if(imagen2 != NULL)
    	SDL_FreeSurface( imagen2 );
    if(pantallaBase != NULL)
    	SDL_FreeSurface( pantallaBase );
    if(message1 != NULL)
    	SDL_FreeSurface( message1 );
    if(pantallaInicio != NULL)
    	SDL_FreeSurface( pantallaInicio );
    if(pantallaFinTorneo != NULL)
    	SDL_FreeSurface( pantallaFinTorneo );
    if(messageFinTorneo != NULL)
    	SDL_FreeSurface( messageFinTorneo );
    if(messagePuntaje != NULL)
    	SDL_FreeSurface( messagePuntaje );
    if(pantallaEspera != NULL)
    	SDL_FreeSurface( pantallaEspera );
    if(messageEspera != NULL)
    	SDL_FreeSurface( messageEspera );
    if(pantallaError != NULL)
    	SDL_FreeSurface( pantallaError );
    if(messageFinError != NULL)
    	SDL_FreeSurface( messageFinError );

    //Close the font that was used
    if(font != NULL)
    	TTF_CloseFont( font );
*/
    //Quit SDL_ttf
    TTF_Quit();

    /* cierro el sdl */
    SDL_Quit();
}

/* Handler que toma las señal de finalización y la trasmite al thread. */
void signalHandler(int signal){
	return;
}

/*  Aplica una superficie sobre una pantalla base o otra superficie */
void apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip){
    //Holds offsets
    SDL_Rect offset;

    //Get offsets
    offset.x = x;
    offset.y = y;

    //Blit
    SDL_BlitSurface( source, clip, destination, &offset );
}



/* Recibe el nombre por linea de comandos */
bool pantallaDeInicio(){
    //The gets the user's name
    IngresoDeNombre name(pantallaBase, &event, &textColor, font);
    //Quit flag
    bool quit = false;
    //Keep track if whether or not the user has entered their name
    bool nameEntered = false;

    //Set the message
    message1 = TTF_RenderText_Solid( font, "INGRESE SU NOMBRE POR FAVOR", textColor );
    //Apply the background
    apply_surface( 0, 0, pantallaInicio, pantallaBase, NULL);
    //Show the message
    apply_surface( ( ANCHO_RESOLUCION - message1->w )/2 , (( ALTO_RESOLUCION- (message1->h/2) ) / 5) * 3.5, message1, pantallaBase , NULL);
    //Update the screen
    if( SDL_Flip( pantallaBase ) == -1 ){
        return CONST_FALSE;
    }

    //While the user hasn't quit
    while( !nameEntered || quit ){
        //While there's events to handle
        SDL_WaitEvent( &event );

		//If the user hasn't entered their name yet
		if( !nameEntered ){
			//Get user input
			if(!name.handle_input())
				return CONST_FALSE;

			//If the enter key was pressed
			if( ( event.type == SDL_KEYDOWN ) && ( event.key.keysym.sym == SDLK_RETURN ) ){
				//Change the flag
				nameEntered = CONST_TRUE;

				//Free the old message surface
				SDL_FreeSurface( message1 );

				//Change the message
				message1 = TTF_RenderText_Solid( font, "SU NOMBRE ES:", textColor );
			}
		}
		//If the user has Xed out the window
		if( event.type == SDL_QUIT ){
			//Quit the program
			quit = CONST_TRUE;
		}

        //Apply the background
        apply_surface( 0, 0, pantallaInicio, pantallaBase, NULL);
        //Show the message
        apply_surface( ( ANCHO_RESOLUCION - message1->w )/2 , (( ALTO_RESOLUCION- (message1->h/2) ) / 5) * 3.5, message1, pantallaBase , NULL);

        //Show the name on the screen
        name.show_centered();

        //Update the screen
        if( SDL_Flip( pantallaBase ) == -1 ){
            return CONST_FALSE;
        }
    }

    /* Si se presiono salir sale con false */
    if(quit)
    	return CONST_FALSE;

    /* Sino me traigo el nombre obtenido */
    name.getNombre(nombre);

    return CONST_TRUE;
}

/* Libera los recursos y sale */
void salir(){
	liberarRecursos();
	exit(0);
}

/* Limpia la pantalla inicial */
void cleanup(){
    //Free the surfaces
    SDL_FreeSurface( pantallaInicio );
    SDL_FreeSurface( message1 );
};

/* Carga una imagen en una superficie */
SDL_Surface *load_image(const char *path){
    //The image that's loaded
    SDL_Surface* loadedImage = NULL;

    //The optimized surface that will be used
    SDL_Surface* optimizedImage = NULL;

    //Load the image
    loadedImage = IMG_Load( path );

    //If the image loaded
    if( loadedImage != NULL ){
        //Color key surface
        SDL_SetColorKey(loadedImage , SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(loadedImage -> format, 185, 243, 182));

        //Create an optimized surface
        optimizedImage = SDL_DisplayFormat( loadedImage );

        //Free the old surface
        SDL_FreeSurface( loadedImage );
    }

    //Return the optimized surface
    return optimizedImage;
}
